package de.ret_world.openrpgstudio.mapeditor;

import buoy.event.RepaintEvent;
import buoy.event.WidgetMouseEvent;
import buoy.widget.BOutline;
import buoy.widget.BScrollPane;
import buoy.widget.BorderContainer;
import buoy.widget.CustomWidget;
import de.ret_world.openrpgstudio.editor.Project;
import de.ret_world.openrpgstudio.editor.script.Point;
import de.ret_world.openrpgstudio.mapeditor.scripts.Layer;
import de.ret_world.openrpgstudio.mapeditor.scripts.TileMap;
import de.ret_world.openrpgstudio.mapeditor.scripts.TileSet;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import javax.imageio.ImageIO;

/**
 * This is the widget of the MapEditor plugin. This is the human interface with which the user can create TileMaps.
 *
 * @author Emu
 */
public class MapEditorWidget extends BorderContainer {

    public static int REDMARKER = -55;
    private CustomWidget pane;
    private HashMap<TileSet, HashMap<Integer, BufferedImage>> tiles;
    private BScrollPane scrl;
    private TileMap map;
    private Layer selectedLayer;
    private ArrayList<Pointer> pointers;
    private boolean enabled;
    private boolean shadeLayers = true;
    private boolean drawGrid = true;

    public MapEditorWidget(TileMap map) {
        this();
        setMap(map);
    }

    public MapEditorWidget() {
        tiles = new HashMap<>();
        pointers = new ArrayList<>();

        pane = new CustomWidget();
        pane.addEventLink(RepaintEvent.class, this, "paintIt");
        pane.addEventLink(WidgetMouseEvent.class, this, "dispachMouseEvents");
        pane.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
        scrl = new BScrollPane(pane);
        add(BOutline.createBevelBorder(scrl, false), CENTER);
    }

    private void paintIt(RepaintEvent ev) {
        Graphics2D g = ev.getGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        if (map != null && isEnabled()) {
            int height = map.getHeight() * map.getTileHeight();
            int width = map.getWidth() * map.getTileWidth();

            g.setColor(Color.WHITE);
            g.fillRect(0, 0, width, height);

            for (int i = 0; i < map.getLayers().size(); i++) {
                Layer l = map.getLayers().get(i);
                HashMap<Integer, BufferedImage> tls = tiles.get(l.getTileset());
                if (tls != null) {
                    for (Point p : l.getPoints().keySet()) {
                        int tile = l.getPoints().get(p);
                        BufferedImage simg = tls.get(tile);
                        if (simg != null) {
                            g.drawImage(simg, (int) (p.getX() * map.getTileWidth()), (int) (p.getY() * map.getTileHeight()), map.getTileWidth(), map.getTileHeight(), null);
                            if (shadeLayers && !l.equals(selectedLayer)) {
                                g.setColor(new Color(90, 90, 90, 90));
                                g.fillRect((int) (p.getX() * map.getTileWidth()), (int) (p.getY() * map.getTileHeight()), map.getTileWidth(), map.getTileHeight());
                            }
                        }
                    }
                }
            }
            if (!pointers.isEmpty()) {
                for (Pointer p : pointers) {
                    if (p.getIndex() >= 0) {
                        HashMap<Integer, BufferedImage> tls = tiles.get(selectedLayer.getTileset());
                        if (tls != null) {
                            BufferedImage simg = tls.get(p.getIndex());
                            g.drawImage(simg, (int) (p.getPoint().getX() * map.getTileWidth()), (int) (p.getPoint().getY() * map.getTileHeight()), map.getTileWidth(), map.getTileHeight(), null);
                        }
                        g.setColor(new Color(255, 255, 128, 96));
                        g.fillRect((int) (p.getPoint().getX() * map.getTileWidth()), (int) (p.getPoint().getY() * map.getTileHeight()), map.getTileWidth(), map.getTileHeight());
                    } else if (p.getIndex() == REDMARKER) {
                        g.setColor(new Color(255, 0, 0, 96));
                        g.fillRect((int) (p.getPoint().getX() * map.getTileWidth()), (int) (p.getPoint().getY() * map.getTileHeight()), map.getTileWidth(), map.getTileHeight());
                    }
                }
            }
            if (drawGrid) {
                g.setColor(new Color(156, 156, 156, 156));
                for (int x = 0; x < map.getWidth(); x++) {
                    g.drawLine(x * map.getTileWidth(), 0, x * map.getTileWidth(), height);
                }
                for (int y = 0; y < map.getHeight(); y++) {
                    g.drawLine(0, y * map.getTileHeight(), width, y * map.getTileHeight());
                }
            }

            pane.setPreferredSize(new Dimension(width, height));
            scrl.layoutChildren();
        }
    }

    private void dispachMouseEvents(WidgetMouseEvent ev) {
        dispatchEvent(ev);
    }

    public void setShadeLayers(boolean shadeLayers) {
        this.shadeLayers = shadeLayers;
        pane.repaint();
    }

    public void setDrawGrid(boolean drawGrid) {
        this.drawGrid = drawGrid;
        pane.repaint();
    }

    public boolean isShadeLayers() {
        return shadeLayers;
    }

    public boolean isDrawGrid() {
        return drawGrid;
    }

    @Override
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        pane.repaint();
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public void addEventLink(Class eventType, Object target, String method) {
        pane.addEventLink(eventType, target, method);
    }

    @Override
    public void addEventLink(Class eventType, Object target) {
        pane.addEventLink(eventType, target);
    }

    @Override
    public void addEventLink(Class eventType, Object target, Method method) {
        pane.addEventLink(eventType, target, method);
    }

    public void setMap(TileMap map) {
        this.map = map;

        //tiles.clear();
        for (Layer l : map.getLayers()) {
            CalcThread calcThread = new CalcThread();
            calcThread.layer = l;
            calcThread.start();
        }

        pane.repaint();
    }

    public void setSelectedLayer(Layer selectedLayer) {
        this.selectedLayer = selectedLayer;
        pane.repaint();
    }

    public Pointer setPointer(int x, int y, int index) {
        pointers.clear();
        Pointer pointer = null;
        if (!(x < 0 || y < 0)) {
            int height = map.getHeight() * map.getTileHeight();
            int width = map.getWidth() * map.getTileWidth();
            int w = map.getTileWidth();
            int h = map.getTileHeight();
            int cursorX = x / w;
            int cursorY = y / h;
            if (cursorX >= (width / w)) {
                cursorX = (width / w) - 1;
            }
            if (cursorY >= (height / h)) {
                cursorY = (height / h) - 1;
            }

            pointer = new Pointer(new Point(cursorX, cursorY), index);
            pointers.add(pointer);
        }
        pane.repaint();
        return pointer;
    }

    public void addPointer(Pointer p) {
        pointers.add(p);
        pane.repaint();
    }

    public ArrayList<Pointer> getPointers() {
        return pointers;
    }

    public void setTile(int x, int y, int index) {
        if (selectedLayer == null || x < 0 || y < 0) {
            return;
        } else {
            int height = map.getHeight() * map.getTileHeight();
            int width = map.getWidth() * map.getTileWidth();
            int w = map.getTileWidth();
            int h = map.getTileHeight();
            int tileX = x / w;
            int tileY = y / h;
            if (tileX >= (width / w)) {
                tileX = (width / w) - 1;
            }
            if (tileY >= (height / h)) {
                tileY = (height / h) - 1;
            }

            selectedLayer.getPoints().put(new Point(tileX, tileY), index);
        }
        pane.repaint();
    }

    public void setTile(Pointer p) {
        if (p != null) {
            selectedLayer.getPoints().put(p.getPoint(), p.getIndex());
        }
    }

    private class CalcThread extends Thread {

        protected Layer layer;
        protected boolean stop = false;

        @Override
        public void run() {
            try {
                if (layer.getTileset() != null && layer.getTileset().getImage() != null) {
                    TileSet tileset = layer.getTileset();
                    BufferedImage image = ImageIO.read(Project.getInstance().getFolder("root").resolve(tileset.getImage().getFile()).toFile());

                    HashMap<Integer, BufferedImage> map = tiles.get(tileset);
                    if (map == null) {
                        map = new HashMap<>();
                        tiles.put(tileset, map);

                        for (int i = 0; i < tileset.getTilesPerColumn() * tileset.getTilesPerRow(); i++) {
                            if (tileset.getTiles().size() > i) {
                                Rectangle r = tileset.getTiles().get(i);
                                BufferedImage simg = image.getSubimage(r.x, r.y, r.width, r.height);

                                map.put(i, simg);
                                repaint();
                            }
                            if (stop) {
                                break;
                            }
                        }
                    }

                }
            } catch (IOException ex) {
                org.apache.log4j.Logger.getLogger(TileSetWidget.class).error(ex);
            } catch (ConcurrentModificationException ex) {
                //do nothing
            }
        }
    }

    public void saveMapAsImage(Path path) throws IOException {
        int height = map.getHeight() * map.getTileHeight();
        int width = map.getWidth() * map.getTileWidth();
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR_PRE);
        Graphics2D g = bi.createGraphics();

        for (int i = 0; i < map.getLayers().size(); i++) {
            Layer l = map.getLayers().get(i);
            HashMap<Integer, BufferedImage> tls = tiles.get(l.getTileset());
            if (tls != null) {
                for (Point p : l.getPoints().keySet()) {
                    int tile = l.getPoints().get(p);
                    BufferedImage simg = tls.get(tile);
                    if (simg != null) {
                        g.drawImage(simg, (int) (p.getX() * map.getTileWidth()), (int) (p.getY() * map.getTileHeight()), map.getTileWidth(), map.getTileHeight(), null);
                    }
                }
            }
        }

        ImageIO.write(bi, "png", Files.newOutputStream(path));
    }
}
